Completed
Push — master ( 3c3114...9af846 )
by Ruben de
01:47 queued 01:01
created

api_client.js ➔ doRemainingWalletDataV2_3   B

Complexity

Conditions 1
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
c 1
b 1
f 0
nc 2
dl 0
loc 25
rs 8.8571
nop 3

1 Function

Rating   Name   Duplication   Size   Complexity  
A api_client.js ➔ ... ➔ q.then 0 22 3
1
/* globals onLoadWorkerLoadAsmCrypto */
2
3
var _ = require('lodash'),
4
    q = require('q'),
5
    bitcoin = require('bitcoinjs-lib'),
6
    bitcoinMessage = require('bitcoinjs-message'),
7
8
    bip39 = require("bip39"),
9
    Wallet = require('./wallet'),
10
    RestClient = require('./rest_client'),
11
    Encryption = require('./encryption'),
12
    KeyDerivation = require('./keyderivation'),
13
    EncryptionMnemonic = require('./encryption_mnemonic'),
14
    blocktrail = require('./blocktrail'),
15
    randomBytes = require('randombytes'),
16
    CryptoJS = require('crypto-js'),
17
    webworkifier = require('./webworkifier');
18
19
/**
20
 *
21
 * @param opt
22
 * @returns {*}
23
 */
24
function networkFromOptions(opt) {
25
    if (opt.bitcoinCash) {
26
        if (opt.regtest) {
27
            return bitcoin.networks.bitcoincashregtest;
28
        } else if (opt.testnet) {
29
            return bitcoin.networks.bitcoincashtestnet;
30
        } else {
31
            return bitcoin.networks.bitcoincash;
32
        }
33
    } else {
34
        if (opt.regtest) {
35
            return bitcoin.networks.regtest;
36
        } else if (opt.testnet) {
37
            return bitcoin.networks.testnet;
38
        } else {
39
            return bitcoin.networks.bitcoin;
40
        }
41
    }
42
}
43
44
var useWebWorker = require('./use-webworker')();
45
46
/**
47
 * Bindings to conssume the BlockTrail API
48
 *
49
 * @param options       object{
50
 *                          apiKey: 'API_KEY',
51
 *                          apiSecret: 'API_SECRET',
52
 *                          host: 'defaults to api.blocktrail.com',
53
 *                          network: 'BTC|LTC',
54
 *                          testnet: true|false
55
 *                      }
56
 * @constructor
57
 */
58
var APIClient = function(options) {
59
    var self = this;
60
61
    // handle constructor call without 'new'
62
    if (!(this instanceof APIClient)) {
63
        return new APIClient(options);
64
    }
65
66
    var normalizedNetwork = APIClient.normalizeNetworkFromOptions(options);
67
    options.network = normalizedNetwork[0];
68
    options.testnet = normalizedNetwork[1];
69
    options.regtest = normalizedNetwork[2];
70
    // apiNetwork we allow to be customized for debugging purposes
71
    options.apiNetwork = options.apiNetwork || normalizedNetwork[3];
72
73
    self.bitcoinCash = options.network === "BCC";
74
    self.regtest = options.regtest;
75
    self.testnet = options.testnet;
76
    self.network = networkFromOptions(self);
77
    self.feeSanityCheck = typeof options.feeSanityCheck !== "undefined" ? options.feeSanityCheck : true;
78
    self.feeSanityCheckBaseFeeMultiplier = options.feeSanityCheckBaseFeeMultiplier || 200;
79
80
    /**
81
     * @type RestClient
82
     */
83
    self.client = APIClient.initRestClient(options);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
84
};
85
86
APIClient.normalizeNetworkFromOptions = function(options) {
87
    /* jshint -W071, -W074 */
88
    var network = 'BTC';
89
    var testnet = false;
90
    var regtest = false;
91
    var apiNetwork = "BTC";
0 ignored issues
show
Unused Code introduced by
The assignment to variable apiNetwork seems to be never used. Consider removing it.
Loading history...
92
93
    var prefix;
94
    var done = false;
95
96
    if (options.network) {
97
        var lower = options.network.toLowerCase();
98
99
        var m = lower.match(/^([rt])?(btc|bch|bcc)$/);
100
        if (!m) {
101
            throw new Error("Invalid network [" + options.network + "]");
102
        }
103
104
        if (m[2] === 'btc') {
105
            network = "BTC";
106
        } else {
107
            network = "BCC";
108
        }
109
110
        prefix = m[1];
111
        if (prefix) {
112
            // if there's a prefix then we're "done", won't apply options.regtest and options.testnet after
113
            done = true;
114
            if (prefix === 'r') {
115
                testnet = true;
116
                regtest = true;
117
            } else if (prefix === 't') {
118
                testnet = true;
119
            }
120
        }
121
    }
122
123
    // if we're not already done then apply options.regtest and options.testnet
124
    if (!done) {
125
        if (options.regtest) {
126
            testnet = true;
127
            regtest = true;
128
            prefix = "r";
129
        } else if (options.testnet) {
130
            testnet = true;
131
            prefix = "t";
132
        }
133
    }
134
135
    apiNetwork = (prefix || "") + network;
136
137
    return [network, testnet, regtest, apiNetwork];
138
};
139
140
APIClient.initRestClient = function(options) {
141
    // BLOCKTRAIL_SDK_API_ENDPOINT overwrite for development
142
    if (process.env.BLOCKTRAIL_SDK_API_ENDPOINT) {
143
        options.host = process.env.BLOCKTRAIL_SDK_API_ENDPOINT;
144
    }
145
146
    // trim off leading https?://
147
    if (options.host && options.host.indexOf("https://") === 0) {
148
        options.https = true;
149
        options.host = options.host.substr(8);
150
    } else if (options.host && options.host.indexOf("http://") === 0) {
151
        options.https = false;
152
        options.host = options.host.substr(7);
153
    }
154
155
    if (typeof options.https === "undefined") {
156
        options.https = true;
157
    }
158
159
    if (!options.host) {
160
        options.host = 'api.blocktrail.com';
161
    }
162
163
    if (!options.port) {
164
        options.port = options.https ? 443 : 80;
165
    }
166
167
    if (!options.endpoint) {
168
        options.endpoint = "/" + (options.apiVersion || "v1") + (options.apiNetwork ? ("/" + options.apiNetwork) : "");
169
    }
170
171
    return new RestClient(options);
172
};
173
174
var determineDataStorageV2_3 = function(options) {
175
    return q.when(options)
176
        .then(function(options) {
177
            // legacy
178
            if (options.storePrimaryMnemonic) {
179
                options.storeDataOnServer = options.storePrimaryMnemonic;
180
            }
181
182
            // storeDataOnServer=false when primarySeed is provided
183
            if (typeof options.storeDataOnServer === "undefined") {
184
                options.storeDataOnServer = !options.primarySeed;
185
            }
186
187
            return options;
188
        });
189
};
190
191
var produceEncryptedDataV2 = function(options, notify) {
192
    return q.when(options)
193
        .then(function(options) {
194
            if (options.storeDataOnServer) {
195
                if (!options.secret) {
196
                    if (!options.passphrase) {
197
                        throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
198
                    }
199
200
                    notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
201
202
                    options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
203
                    options.encryptedSecret = CryptoJS.AES.encrypt(options.secret, options.passphrase).toString(CryptoJS.format.OpenSSL); // 'base64' string
204
                }
205
206
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
207
208
                options.encryptedPrimarySeed = CryptoJS.AES.encrypt(options.primarySeed.toString('base64'), options.secret)
209
                    .toString(CryptoJS.format.OpenSSL); // 'base64' string
210
                options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8).toString('hex'); // string because we use it as passphrase
211
212
                notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
213
214
                options.recoveryEncryptedSecret = CryptoJS.AES.encrypt(options.secret, options.recoverySecret)
215
                                                              .toString(CryptoJS.format.OpenSSL); // 'base64' string
216
            }
217
218
            return options;
219
        });
220
};
221
222
APIClient.prototype.promisedEncrypt = function(pt, pw, iter) {
223
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
224
        // generate randomness outside of webworker because many browsers don't have crypto.getRandomValues inside webworkers
225
        var saltBuf = Encryption.generateSalt();
226
        var iv = Encryption.generateIV();
227
228
        return webworkifier.workify(APIClient.prototype.promisedEncrypt, function factory() {
229
            return require('./webworker');
230
        }, onLoadWorkerLoadAsmCrypto, {
231
            method: 'Encryption.encryptWithSaltAndIV',
232
            pt: pt,
233
            pw: pw,
234
            saltBuf: saltBuf,
235
            iv: iv,
236
            iterations: iter
237
        })
238
            .then(function(data) {
239
                return Buffer.from(data.cipherText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
240
            });
241
    } else {
242
        try {
243
            return q.when(Encryption.encrypt(pt, pw, iter));
244
        } catch (e) {
245
            return q.reject(e);
246
        }
247
    }
248
};
249
250
APIClient.prototype.promisedDecrypt = function(ct, pw) {
251
    if (useWebWorker && typeof onLoadWorkerLoadAsmCrypto === "function") {
252
        return webworkifier.workify(APIClient.prototype.promisedDecrypt, function() {
253
            return require('./webworker');
254
        }, onLoadWorkerLoadAsmCrypto, {
255
            method: 'Encryption.decrypt',
256
            ct: ct,
257
            pw: pw
258
        })
259
            .then(function(data) {
260
                return Buffer.from(data.plainText.buffer);
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
261
            });
262
    } else {
263
        try {
264
            return q.when(Encryption.decrypt(ct, pw));
265
        } catch (e) {
266
            return q.reject(e);
267
        }
268
    }
269
};
270
271
APIClient.prototype.produceEncryptedDataV3 = function(options, notify) {
272
    var self = this;
273
274
    return q.when(options)
275
        .then(function(options) {
276
            if (options.storeDataOnServer) {
277
                return q.when()
278
                    .then(function() {
279
                        if (!options.secret) {
280
                            if (!options.passphrase) {
281
                                throw new blocktrail.WalletCreateError("Can't encrypt data without a passphrase");
282
                            }
283
284
                            notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET);
285
286
                            // -> now a buffer
287
                            options.secret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
288
289
                            // -> now a buffer
290
                            return self.promisedEncrypt(options.secret, new Buffer(options.passphrase), KeyDerivation.defaultIterations)
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
291
                                .then(function(encryptedSecret) {
292
                                    options.encryptedSecret = encryptedSecret;
293
                                });
294
                        } else {
295
                            if (!(options.secret instanceof Buffer)) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !(options.secret instanceof Buffer) is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
296
                                throw new Error('Secret must be a buffer');
297
                            }
298
                        }
299
                    })
300
                    .then(function() {
301
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY);
302
303
                        return self.promisedEncrypt(options.primarySeed, options.secret, KeyDerivation.subkeyIterations)
304
                            .then(function(encryptedPrimarySeed) {
305
                                options.encryptedPrimarySeed = encryptedPrimarySeed;
306
                            });
307
                    })
308
                    .then(function() {
309
                        // skip generating recovery secret when explicitly set to false
310
                        if (options.recoverySecret === false) {
311
                            return;
312
                        }
313
314
                        notify(APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY);
315
                        if (!options.recoverySecret) {
316
                            options.recoverySecret = randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
317
                        }
318
319
                        return self.promisedEncrypt(options.secret, options.recoverySecret, KeyDerivation.defaultIterations)
320
                            .then(function(recoveryEncryptedSecret) {
321
                                options.recoveryEncryptedSecret = recoveryEncryptedSecret;
322
                            });
323
                    })
324
                    .then(function() {
325
                        return options;
326
                    });
327
            } else {
328
                return options;
329
            }
330
        });
331
};
332
333
var doRemainingWalletDataV2_3 = function(options, network, notify) {
334
    return q.when(options)
335
        .then(function(options) {
336
            if (!options.backupPublicKey) {
337
                options.backupSeed = options.backupSeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
338
            }
339
340
            notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
341
342
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, network);
343
344
            notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
345
346
            if (!options.backupPublicKey) {
347
                options.backupPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.backupSeed, network);
348
                options.backupPublicKey = options.backupPrivateKey.neutered();
349
            }
350
351
            options.primaryPublicKey = options.primaryPrivateKey.deriveHardened(options.keyIndex).neutered();
352
353
            notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
354
355
            return options;
356
        });
357
};
358
359
APIClient.prototype.mnemonicToPrivateKey = function(mnemonic, passphrase, cb) {
360
    var self = this;
361
362
    var deferred = q.defer();
363
    deferred.promise.spreadNodeify(cb);
364
365
    deferred.resolve(q.fcall(function() {
366
        return self.mnemonicToSeedHex(mnemonic, passphrase).then(function(seedHex) {
367
            return bitcoin.HDNode.fromSeedHex(seedHex, self.network);
368
        });
369
    }));
370
371
    return deferred.promise;
372
};
373
374
APIClient.prototype.mnemonicToSeedHex = function(mnemonic, passphrase) {
375
    var self = this;
376
377
    if (useWebWorker) {
378
        return webworkifier.workify(self.mnemonicToSeedHex, function() {
379
            return require('./webworker');
380
        }, {method: 'mnemonicToSeedHex', mnemonic: mnemonic, passphrase: passphrase})
381
            .then(function(data) {
382
                return data.seed;
383
            });
384
    } else {
385
        try {
386
            return q.when(bip39.mnemonicToSeedHex(mnemonic, passphrase));
387
        } catch (e) {
388
            return q.reject(e);
389
        }
390
    }
391
};
392
393
APIClient.prototype.resolvePrimaryPrivateKeyFromOptions = function(options, cb) {
394
    var self = this;
395
396
    var deferred = q.defer();
397
    deferred.promise.nodeify(cb);
398
399
    try {
400
        // avoid conflicting options
401
        if (options.passphrase && options.password) {
402
            throw new blocktrail.WalletCreateError("Can't specify passphrase and password");
403
        }
404
        // normalize passphrase/password
405
        options.passphrase = options.passphrase || options.password;
406
        delete options.password;
407
408
        // avoid conflicting options
409
        if (options.primaryMnemonic && options.primarySeed) {
410
            throw new blocktrail.WalletInitError("Can only specify one of; Primary Mnemonic or Primary Seed");
411
        }
412
413
        // avoid deprecated options
414
        if (options.primaryPrivateKey) {
415
            throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
416
        }
417
418
        // make sure we have at least one thing to use
419
        if (!options.primaryMnemonic && !options.primarySeed) {
420
            throw new blocktrail.WalletInitError("Need to specify at least one of; Primary Mnemonic or Primary Seed");
421
        }
422
423
        if (options.primarySeed) {
424
            self.primarySeed = options.primarySeed;
425
            options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(self.primarySeed, self.network);
426
            deferred.resolve(options);
427
        } else {
428
            if (!options.passphrase) {
429
                throw new blocktrail.WalletInitError("Can't init wallet with Primary Mnemonic without a passphrase");
430
            }
431
432
            self.mnemonicToSeedHex(options.primaryMnemonic, options.passphrase)
433
                .then(function(seedHex) {
434
                    try {
435
                        options.primarySeed = new Buffer(seedHex, 'hex');
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
436
                        options.primaryPrivateKey = bitcoin.HDNode.fromSeedBuffer(options.primarySeed, self.network);
437
                        deferred.resolve(options);
438
                    } catch (e) {
439
                        deferred.reject(e);
440
                    }
441
                }, function(e) {
442
                    deferred.reject(e);
443
                });
444
        }
445
    } catch (e) {
446
        deferred.reject(e);
447
    }
448
449
    return deferred.promise;
450
};
451
452
APIClient.prototype.resolveBackupPublicKeyFromOptions = function(options, cb) {
453
    var self = this;
454
455
    var deferred = q.defer();
456
    deferred.promise.nodeify(cb);
457
458
    try {
459
        // avoid conflicting options
460
        if (options.backupMnemonic && options.backupPublicKey) {
461
            throw new blocktrail.WalletInitError("Can only specify one of; Backup Mnemonic or Backup PublicKey");
462
        }
463
464
        // make sure we have at least one thing to use
465
        if (!options.backupMnemonic && !options.backupPublicKey) {
466
            throw new blocktrail.WalletInitError("Need to specify at least one of; Backup Mnemonic or Backup PublicKey");
467
        }
468
469
        if (options.backupPublicKey) {
470
            if (options.backupPublicKey instanceof bitcoin.HDNode) {
471
                deferred.resolve(options);
472
            } else {
473
                options.backupPublicKey = bitcoin.HDNode.fromBase58(options.backupPublicKey, self.network);
474
                deferred.resolve(options);
475
            }
476
        } else {
477
            self.mnemonicToPrivateKey(options.backupMnemonic, "").then(function(backupPrivateKey) {
478
                options.backupPublicKey = backupPrivateKey.neutered();
479
                deferred.resolve(options);
480
            }, function(e) {
481
                deferred.reject(e);
482
            });
483
        }
484
    } catch (e) {
485
        deferred.reject(e);
486
    }
487
488
    return deferred.promise;
489
};
490
491
APIClient.prototype.debugAuth = function(cb) {
492
    var self = this;
493
494
    return self.client.get("/debug/http-signature", null, true, cb);
495
};
496
497
/**
498
 * get a single address
499
 *
500
 * @param address      string       address hash
501
 * @param [cb]          function    callback function to call when request is complete
502
 * @return q.Promise
503
 */
504
APIClient.prototype.address = function(address, cb) {
505
    var self = this;
506
507
    return self.client.get("/address/" + address, null, cb);
508
};
509
510
APIClient.prototype.addresses = function(addresses, cb) {
511
    var self = this;
512
513
    return self.client.post("/address", null, {"addresses": addresses}, cb);
514
};
515
516
/**
517
 * get all transactions for an address (paginated)
518
 *
519
 * @param address       string      address hash
520
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
521
 * @param [cb]          function    callback function to call when request is complete
522
 * @return q.Promise
523
 */
524
APIClient.prototype.addressTransactions = function(address, params, cb) {
525
    var self = this;
526
527
    if (typeof params === "function") {
528
        cb = params;
529
        params = null;
530
    }
531
532
    return self.client.get("/address/" + address + "/transactions", params, cb);
533
};
534
535
/**
536
 * get all transactions for a batch of addresses (paginated)
537
 *
538
 * @param addresses     array       address hashes
539
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
540
 * @param [cb]          function    callback function to call when request is complete
541
 * @return q.Promise
542
 */
543
APIClient.prototype.batchAddressHasTransactions = function(addresses, params, cb) {
544
    var self = this;
545
546
    if (typeof params === "function") {
547
        cb = params;
548
        params = null;
549
    }
550
551
    return self.client.post("/address/has-transactions", params, {"addresses": addresses}, cb);
552
};
553
554
/**
555
 * get all unconfirmed transactions for an address (paginated)
556
 *
557
 * @param address       string      address hash
558
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
559
 * @param [cb]          function    callback function to call when request is complete
560
 * @return q.Promise
561
 */
562
APIClient.prototype.addressUnconfirmedTransactions = function(address, params, cb) {
563
    var self = this;
564
565
    if (typeof params === "function") {
566
        cb = params;
567
        params = null;
568
    }
569
570
    return self.client.get("/address/" + address + "/unconfirmed-transactions", params, cb);
571
};
572
573
/**
574
 * get all unspent outputs for an address (paginated)
575
 *
576
 * @param address       string      address hash
577
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
578
 * @param [cb]          function    callback function to call when request is complete
579
 * @return q.Promise
580
 */
581
APIClient.prototype.addressUnspentOutputs = function(address, params, cb) {
582
    var self = this;
583
584
    if (typeof params === "function") {
585
        cb = params;
586
        params = null;
587
    }
588
589
    return self.client.get("/address/" + address + "/unspent-outputs", params, cb);
590
};
591
592
/**
593
 * get all unspent outputs for a batch of addresses (paginated)
594
 *
595
 * @param addresses     array       address hashes
596
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
597
 * @param [cb]          function    callback function to call when request is complete
598
 * @return q.Promise
599
 */
600
APIClient.prototype.batchAddressUnspentOutputs = function(addresses, params, cb) {
601
    var self = this;
602
603
    if (typeof params === "function") {
604
        cb = params;
605
        params = null;
606
    }
607
608
    return self.client.post("/address/unspent-outputs", params, {"addresses": addresses}, cb);
609
};
610
611
/**
612
 * verify ownership of an address
613
 *
614
 * @param address       string      address hash
615
 * @param signature     string      a signed message (the address hash) using the private key of the address
616
 * @param [cb]          function    callback function to call when request is complete
617
 * @return q.Promise
618
 */
619
APIClient.prototype.verifyAddress = function(address, signature, cb) {
620
    var self = this;
621
622
    return self.client.post("/address/" + address + "/verify", null, {signature: signature}, cb);
623
};
624
625
/**
626
 * get all blocks (paginated)
627
 *
628
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
629
 * @param [cb]          function    callback function to call when request is complete
630
 * @return q.Promise
631
 */
632
APIClient.prototype.allBlocks = function(params, cb) {
633
    var self = this;
634
635
    if (typeof params === "function") {
636
        cb = params;
637
        params = null;
638
    }
639
640
    return self.client.get("/all-blocks", params, cb);
641
};
642
643
/**
644
 * get a block
645
 *
646
 * @param block         string|int  a block hash or a block height
647
 * @param [cb]          function    callback function to call when request is complete
648
 * @return q.Promise
649
 */
650
APIClient.prototype.block = function(block, cb) {
651
    var self = this;
652
653
    return self.client.get("/block/" + block, null, cb);
654
};
655
656
/**
657
 * get the latest block
658
 *
659
 * @param [cb]          function    callback function to call when request is complete
660
 * @return q.Promise
661
 */
662
APIClient.prototype.blockLatest = function(cb) {
663
    var self = this;
664
665
    return self.client.get("/block/latest", null, cb);
666
};
667
668
/**
669
 * get all transactions for a block (paginated)
670
 *
671
 * @param block         string|int  a block hash or a block height
672
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
673
 * @param [cb]          function    callback function to call when request is complete
674
 * @return q.Promise
675
 */
676
APIClient.prototype.blockTransactions = function(block, params, cb) {
677
    var self = this;
678
679
    if (typeof params === "function") {
680
        cb = params;
681
        params = null;
682
    }
683
684
    return self.client.get("/block/" + block + "/transactions", params, cb);
685
};
686
687
/**
688
 * get a single transaction
689
 *
690
 * @param tx            string      transaction hash
691
 * @param [cb]          function    callback function to call when request is complete
692
 * @return q.Promise
693
 */
694
APIClient.prototype.transaction = function(tx, cb) {
695
    var self = this;
696
697
    return self.client.get("/transaction/" + tx, null, cb);
698
};
699
700
/**
701
 * get a batch of transactions
702
 *
703
 * @param txs           string[]    list of transaction hashes (txId)
704
 * @param [cb]          function    callback function to call when request is complete
705
 * @return q.Promise
706
 */
707
APIClient.prototype.transactions = function(txs, cb) {
708
    var self = this;
709
710
    return self.client.post("/transactions", null, txs, cb, false);
711
};
712
713
/**
714
 * get a paginated list of all webhooks associated with the api user
715
 *
716
 * @param [params]      object      pagination: {page: 1, limit: 20}
717
 * @param [cb]          function    callback function to call when request is complete
718
 * @return q.Promise
719
 */
720
APIClient.prototype.allWebhooks = function(params, cb) {
721
    var self = this;
722
723
    if (typeof params === "function") {
724
        cb = params;
725
        params = null;
726
    }
727
728
    return self.client.get("/webhooks", params, cb);
729
};
730
731
/**
732
 * create a new webhook
733
 *
734
 * @param url           string      the url to receive the webhook events
735
 * @param [identifier]  string      a unique identifier associated with the webhook
736
 * @param [cb]          function    callback function to call when request is complete
737
 * @return q.Promise
738
 */
739
APIClient.prototype.setupWebhook = function(url, identifier, cb) {
740
    var self = this;
741
742
    if (typeof identifier === "function") {
743
        //mimic function overloading
744
        cb = identifier;
745
        identifier = null;
746
    }
747
748
    return self.client.post("/webhook", null, {url: url, identifier: identifier}, cb);
749
};
750
751
/**
752
 * Converts a cash address to the legacy (base58) format
753
 * @param {string} input
754
 * @returns {string}
755
 */
756
APIClient.prototype.getLegacyBitcoinCashAddress = function(input) {
757
    if (this.network === bitcoin.networks.bitcoincash ||
758
        this.network === bitcoin.networks.bitcoincashtestnet ||
759
        this.network === bitcoin.networks.bitcoincashregtest) {
760
        var address;
761
        try {
762
            bitcoin.address.fromBase58Check(input, this.network);
763
            return input;
764
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
765
766
        address = bitcoin.address.fromCashAddress(input, this.network);
767
        var prefix;
768
        if (address.version === bitcoin.script.types.P2PKH) {
769
            prefix = this.network.pubKeyHash;
770
        } else if (address.version === bitcoin.script.types.P2SH) {
771
            prefix = this.network.scriptHash;
772
        } else {
773
            throw new Error("Unsupported address type");
774
        }
775
776
        return bitcoin.address.toBase58Check(address.hash, prefix);
777
    }
778
779
    throw new Error("Cash addresses only work on bitcoin cash");
780
};
781
782
/**
783
 * Converts a legacy bitcoin to the new cashaddr format
784
 * @param {string} input
785
 * @returns {string}
786
 */
787
APIClient.prototype.getCashAddressFromLegacyAddress = function(input) {
788
    if (this.network === bitcoin.networks.bitcoincash ||
789
        this.network === bitcoin.networks.bitcoincashtestnet ||
790
        this.network === bitcoin.networks.bitcoincashregtest
791
    ) {
792
        var address;
793
        try {
794
            bitcoin.address.fromCashAddress(input, this.network);
795
            return input;
796
        } catch (e) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
797
798
        address = bitcoin.address.fromBase58Check(input, this.network);
799
        var scriptType;
800
        if (address.version === this.network.pubKeyHash) {
801
            scriptType = bitcoin.script.types.P2PKH;
802
        } else if (address.version === this.network.scriptHash) {
803
            scriptType = bitcoin.script.types.P2SH;
804
        } else {
805
            throw new Error("Unsupported address type");
806
        }
807
808
        return bitcoin.address.toCashAddress(address.hash, scriptType, this.network.cashAddrPrefix);
809
    }
810
811
    throw new Error("Cash addresses only work on bitcoin cash");
812
};
813
814
/**
815
 * get an existing webhook by it's identifier
816
 *
817
 * @param identifier    string      the unique identifier of the webhook to get
818
 * @param [cb]          function    callback function to call when request is complete
819
 * @return q.Promise
820
 */
821
APIClient.prototype.getWebhook = function(identifier, cb) {
822
    var self = this;
823
824
    return self.client.get("/webhook/" + identifier, null, cb);
825
};
826
827
/**
828
 * update an existing webhook
829
 *
830
 * @param identifier    string      the unique identifier of the webhook
831
 * @param webhookData   object      the data to update: {identifier: newIdentifier, url:newUrl}
832
 * @param [cb]          function    callback function to call when request is complete
833
 * @return q.Promise
834
 */
835
APIClient.prototype.updateWebhook = function(identifier, webhookData, cb) {
836
    var self = this;
837
838
    return self.client.put("/webhook/" + identifier, null, webhookData, cb);
839
};
840
841
/**
842
 * deletes an existing webhook and any event subscriptions associated with it
843
 *
844
 * @param identifier    string      the unique identifier of the webhook
845
 * @param [cb]          function    callback function to call when request is complete
846
 * @return q.Promise
847
 */
848
APIClient.prototype.deleteWebhook = function(identifier, cb) {
849
    var self = this;
850
851
    return self.client.delete("/webhook/" + identifier, null, null, cb);
852
};
853
854
/**
855
 * get a paginated list of all the events a webhook is subscribed to
856
 *
857
 * @param identifier    string      the unique identifier of the webhook
858
 * @param [params]      object      pagination: {page: 1, limit: 20}
859
 * @param [cb]          function    callback function to call when request is complete
860
 * @return q.Promise
861
 */
862
APIClient.prototype.getWebhookEvents = function(identifier, params, cb) {
863
    var self = this;
864
865
    if (typeof params === "function") {
866
        cb = params;
867
        params = null;
868
    }
869
870
    return self.client.get("/webhook/" + identifier + "/events", params, cb);
871
};
872
873
/**
874
 * subscribes a webhook to transaction events for a particular transaction
875
 *
876
 * @param identifier    string      the unique identifier of the webhook
877
 * @param transaction   string      the transaction hash
878
 * @param confirmations integer     the amount of confirmations to send
879
 * @param [cb]          function    callback function to call when request is complete
880
 * @return q.Promise
881
 */
882
APIClient.prototype.subscribeTransaction = function(identifier, transaction, confirmations, cb) {
883
    var self = this;
884
    var postData = {
885
        'event_type': 'transaction',
886
        'transaction': transaction,
887
        'confirmations': confirmations
888
    };
889
890
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
891
};
892
893
/**
894
 * subscribes a webhook to transaction events on a particular address
895
 *
896
 * @param identifier    string      the unique identifier of the webhook
897
 * @param address       string      the address hash
898
 * @param confirmations integer     the amount of confirmations to send
899
 * @param [cb]          function    callback function to call when request is complete
900
 * @return q.Promise
901
 */
902
APIClient.prototype.subscribeAddressTransactions = function(identifier, address, confirmations, cb) {
903
    var self = this;
904
    var postData = {
905
        'event_type': 'address-transactions',
906
        'address': address,
907
        'confirmations': confirmations
908
    };
909
910
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
911
};
912
913
/**
914
 * batch subscribes a webhook to multiple transaction events
915
 *
916
 * @param  identifier   string      the unique identifier of the webhook
917
 * @param  batchData    array       An array of objects containing batch event data:
918
 *                                  {address : 'address', confirmations : 'confirmations']
919
 *                                  where address is the address to subscribe to and confirmations (optional) is the amount of confirmations to send
920
 * @param [cb]          function    callback function to call when request is complete
921
 * @return q.Promise
922
 */
923
APIClient.prototype.batchSubscribeAddressTransactions = function(identifier, batchData, cb) {
924
    var self = this;
925
    batchData.forEach(function(record) {
926
        record.event_type = 'address-transactions';
927
    });
928
929
    return self.client.post("/webhook/" + identifier + "/events/batch", null, batchData, cb);
930
};
931
932
/**
933
 * subscribes a webhook to a new block event
934
 *
935
 * @param identifier    string      the unique identifier of the webhook
936
 * @param [cb]          function    callback function to call when request is complete
937
 * @return q.Promise
938
 */
939
APIClient.prototype.subscribeNewBlocks = function(identifier, cb) {
940
    var self = this;
941
    var postData = {
942
        'event_type': 'block'
943
    };
944
945
    return self.client.post("/webhook/" + identifier + "/events", null, postData, cb);
946
};
947
948
/**
949
 * removes an address transaction event subscription from a webhook
950
 *
951
 * @param identifier    string      the unique identifier of the webhook
952
 * @param address       string      the address hash
953
 * @param [cb]          function    callback function to call when request is complete
954
 * @return q.Promise
955
 */
956
APIClient.prototype.unsubscribeAddressTransactions = function(identifier, address, cb) {
957
    var self = this;
958
959
    return self.client.delete("/webhook/" + identifier + "/address-transactions/" + address, null, null, cb);
960
};
961
962
/**
963
 * removes an transaction event subscription from a webhook
964
 *
965
 * @param identifier    string      the unique identifier of the webhook
966
 * @param transaction   string      the transaction hash
967
 * @param [cb]          function    callback function to call when request is complete
968
 * @return q.Promise
969
 */
970
APIClient.prototype.unsubscribeTransaction = function(identifier, transaction, cb) {
971
    var self = this;
972
973
    return self.client.delete("/webhook/" + identifier + "/transaction/" + transaction, null, null, cb);
974
};
975
976
/**
977
 * removes a block event subscription from a webhook
978
 *
979
 * @param identifier    string      the unique identifier of the webhook
980
 * @param [cb]          function    callback function to call when request is complete
981
 * @return q.Promise
982
 */
983
APIClient.prototype.unsubscribeNewBlocks = function(identifier, cb) {
984
    var self = this;
985
986
    return self.client.delete("/webhook/" + identifier + "/block", null, null, cb);
987
};
988
989
/**
990
 * initialize an existing wallet
991
 *
992
 * Either takes two argument:
993
 * @param options       object      {}
994
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
995
 *
996
 * Or takes three arguments (old, deprecated syntax):
997
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
998
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
999
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 994. The second definition is ignored.
Loading history...
1000
 *
1001
 * @returns {q.Promise}
1002
 */
1003
APIClient.prototype.initWallet = function(options, cb) {
1004
    var self = this;
1005
1006
    if (typeof options !== "object") {
1007
        // get the old-style arguments
1008
        options = {
1009
            identifier: arguments[0],
1010
            passphrase: arguments[1]
1011
        };
1012
1013
        cb = arguments[2];
1014
    }
1015
1016
    if (options.check_backup_key) {
1017
        if (typeof options.check_backup_key !== "string") {
1018
            throw new Error("Invalid input, must provide the backup key as a string (the xpub)");
1019
        }
1020
    }
1021
1022
    var deferred = q.defer();
1023
    deferred.promise.spreadNodeify(cb);
1024
1025
    var identifier = options.identifier;
1026
1027
    if (!identifier) {
1028
        deferred.reject(new blocktrail.WalletInitError("Identifier is required"));
1029
        return deferred.promise;
1030
    }
1031
1032
    deferred.resolve(self.client.get("/wallet/" + identifier, null, true).then(function(result) {
1033
        var keyIndex = options.keyIndex || result.key_index;
1034
1035
        options.walletVersion = result.wallet_version;
1036
1037
        if (options.check_backup_key) {
1038
            if (options.check_backup_key !== result.backup_public_key[0]) {
1039
                throw new Error("Backup key returned from server didn't match our own copy");
1040
            }
1041
        }
1042
        var backupPublicKey = bitcoin.HDNode.fromBase58(result.backup_public_key[0], self.network);
1043
        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1044
            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1045
        });
1046
        var primaryPublicKeys = _.mapValues(result.primary_public_keys, function(primaryPublicKey) {
1047
            return bitcoin.HDNode.fromBase58(primaryPublicKey[0], self.network);
1048
        });
1049
1050
        // initialize wallet
1051
        var wallet = new Wallet(
1052
            self,
1053
            identifier,
1054
            options.walletVersion,
1055
            result.primary_mnemonic,
1056
            result.encrypted_primary_seed,
1057
            result.encrypted_secret,
1058
            primaryPublicKeys,
1059
            backupPublicKey,
1060
            blocktrailPublicKeys,
1061
            keyIndex,
1062
            result.segwit || 0,
1063
            self.testnet,
1064
            self.regtest,
1065
            result.checksum,
1066
            result.upgrade_key_index,
1067
            options.useCashAddress,
1068
            options.bypassNewAddressCheck
1069
        );
1070
1071
        wallet.recoverySecret = result.recovery_secret;
1072
1073
        if (!options.readOnly) {
1074
            return wallet.unlock(options).then(function() {
1075
                return wallet;
1076
            });
1077
        } else {
1078
            return wallet;
1079
        }
1080
    }));
1081
1082
    return deferred.promise;
1083
};
1084
1085
APIClient.CREATE_WALLET_PROGRESS_START = 0;
1086
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_SECRET = 4;
1087
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_PRIMARY = 5;
1088
APIClient.CREATE_WALLET_PROGRESS_ENCRYPT_RECOVERY = 6;
1089
APIClient.CREATE_WALLET_PROGRESS_PRIMARY = 10;
1090
APIClient.CREATE_WALLET_PROGRESS_BACKUP = 20;
1091
APIClient.CREATE_WALLET_PROGRESS_SUBMIT = 30;
1092
APIClient.CREATE_WALLET_PROGRESS_INIT = 40;
1093
APIClient.CREATE_WALLET_PROGRESS_DONE = 100;
1094
1095
/**
1096
 * create a new wallet
1097
 *   - will generate a new primary seed and backup seed
1098
 *
1099
 * Either takes two argument:
1100
 * @param options       object      {}
1101
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys) // nocommit @TODO
1102
 *
1103
 * For v1 wallets (explicitly specify options.walletVersion=v1):
1104
 * @param options       object      {}
0 ignored issues
show
Documentation introduced by
The parameter options has already been documented on line 1100. The second definition is ignored.
Loading history...
1105
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1101. The second definition is ignored.
Loading history...
1106
 *
1107
 * Or takes four arguments (old, deprecated syntax):
1108
 * @param identifier    string      the wallet identifier to be initialized
0 ignored issues
show
Documentation introduced by
The parameter identifier does not exist. Did you maybe forget to remove this comment?
Loading history...
1109
 * @param passphrase    string      the password to decrypt the mnemonic with
0 ignored issues
show
Documentation introduced by
The parameter passphrase does not exist. Did you maybe forget to remove this comment?
Loading history...
1110
 * @param keyIndex      int         override for the blocktrail cosign key to use (for development purposes)
0 ignored issues
show
Documentation introduced by
The parameter keyIndex does not exist. Did you maybe forget to remove this comment?
Loading history...
1111
 * @param [cb]          function    callback(err, wallet, primaryMnemonic, backupMnemonic, blocktrailPubKeys)
0 ignored issues
show
Documentation introduced by
The parameter cb has already been documented on line 1101. The second definition is ignored.
Loading history...
1112
 * @returns {q.Promise}
1113
 */
1114
APIClient.prototype.createNewWallet = function(options, cb) {
1115
    /* jshint -W071, -W074 */
1116
1117
    var self = this;
1118
1119
    if (typeof options !== "object") {
1120
        // get the old-style arguments
1121
        var identifier = arguments[0];
1122
        var passphrase = arguments[1];
1123
        var keyIndex = arguments[2];
1124
        cb = arguments[3];
1125
1126
        // keyIndex is optional
1127
        if (typeof keyIndex === "function") {
1128
            cb = keyIndex;
1129
            keyIndex = null;
1130
        }
1131
1132
        options = {
1133
            identifier: identifier,
1134
            passphrase: passphrase,
1135
            keyIndex: keyIndex
1136
        };
1137
    }
1138
1139
    // default to v3
1140
    options.walletVersion = options.walletVersion || Wallet.WALLET_VERSION_V3;
1141
1142
    var deferred = q.defer();
1143
    deferred.promise.spreadNodeify(cb);
1144
1145
    q.nextTick(function() {
1146
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_START);
1147
1148
        options.keyIndex = options.keyIndex || 0;
1149
        options.passphrase = options.passphrase || options.password;
1150
        delete options.password;
1151
1152
        if (!options.identifier) {
1153
            deferred.reject(new blocktrail.WalletCreateError("Identifier is required"));
1154
            return deferred.promise;
1155
        }
1156
1157
        if (options.walletVersion === Wallet.WALLET_VERSION_V1) {
1158
            self._createNewWalletV1(options)
1159
                .progress(function(p) { deferred.notify(p); })
1160
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1161
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1162
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V2) {
1163
            self._createNewWalletV2(options)
1164
                .progress(function(p) { deferred.notify(p); })
1165
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1166
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1167
        } else if (options.walletVersion === Wallet.WALLET_VERSION_V3) {
1168
            self._createNewWalletV3(options)
1169
                .progress(function(p) { deferred.notify(p); })
1170
                .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); })
1171
            ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1172
        } else {
1173
            deferred.reject(new blocktrail.WalletCreateError("Invalid wallet version!"));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1174
        }
1175
    });
1176
1177
    return deferred.promise;
1178
};
1179
1180
APIClient.prototype._createNewWalletV1 = function(options) {
1181
    var self = this;
1182
1183
    var deferred = q.defer();
1184
1185
    q.nextTick(function() {
1186
1187
        if (!options.primaryMnemonic && !options.primarySeed) {
1188
            if (!options.passphrase && !options.password) {
1189
                deferred.reject(new blocktrail.WalletCreateError("Can't generate Primary Mnemonic without a passphrase"));
1190
                return deferred.promise;
1191
            } else {
1192
                options.primaryMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1193
                if (options.storePrimaryMnemonic !== false) {
1194
                    options.storePrimaryMnemonic = true;
1195
                }
1196
            }
1197
        }
1198
1199
        if (!options.backupMnemonic && !options.backupPublicKey) {
1200
            options.backupMnemonic = bip39.generateMnemonic(Wallet.WALLET_ENTROPY_BITS);
1201
        }
1202
1203
        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_PRIMARY);
1204
1205
        self.resolvePrimaryPrivateKeyFromOptions(options)
1206
            .then(function(options) {
1207
                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_BACKUP);
1208
1209
                return self.resolveBackupPublicKeyFromOptions(options)
1210
                    .then(function(options) {
1211
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_SUBMIT);
1212
1213
                        // create a checksum of our private key which we'll later use to verify we used the right password
1214
                        var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1215
                        var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1216
                        var keyIndex = options.keyIndex;
1217
1218
                        var primaryPublicKey = options.primaryPrivateKey.deriveHardened(keyIndex).neutered();
1219
1220
                        // send the public keys to the server to store them
1221
                        //  and the mnemonic, which is safe because it's useless without the password
1222
                        return self.storeNewWalletV1(
1223
                            options.identifier,
1224
                            [primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1225
                            [options.backupPublicKey.toBase58(), "M"],
1226
                            options.storePrimaryMnemonic ? options.primaryMnemonic : false,
1227
                            checksum,
1228
                            keyIndex,
1229
                            options.segwit || null
1230
                        )
1231
                            .then(function(result) {
1232
                                deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1233
1234
                                var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1235
                                    return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1236
                                });
1237
1238
                                var wallet = new Wallet(
1239
                                    self,
1240
                                    options.identifier,
1241
                                    Wallet.WALLET_VERSION_V1,
1242
                                    options.primaryMnemonic,
1243
                                    null,
1244
                                    null,
1245
                                    {keyIndex: primaryPublicKey},
1246
                                    options.backupPublicKey,
1247
                                    blocktrailPublicKeys,
1248
                                    keyIndex,
1249
                                    result.segwit || 0,
1250
                                    self.testnet,
1251
                                    self.regtest,
1252
                                    checksum,
1253
                                    result.upgrade_key_index,
1254
                                    options.useCashAddress,
1255
                                    options.bypassNewAddressCheck
1256
                                );
1257
1258
                                return wallet.unlock({
1259
                                    walletVersion: Wallet.WALLET_VERSION_V1,
1260
                                    passphrase: options.passphrase,
1261
                                    primarySeed: options.primarySeed,
1262
                                    primaryMnemonic: null // explicit null
1263
                                }).then(function() {
1264
                                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1265
                                    return [
1266
                                        wallet,
1267
                                        {
1268
                                            walletVersion: wallet.walletVersion,
1269
                                            primaryMnemonic: options.primaryMnemonic,
1270
                                            backupMnemonic: options.backupMnemonic,
1271
                                            blocktrailPublicKeys: blocktrailPublicKeys
1272
                                        }
1273
                                    ];
1274
                                });
1275
                            });
1276
                    }
1277
                );
1278
            })
1279
            .then(
1280
            function(r) {
1281
                deferred.resolve(r);
1282
            },
1283
            function(e) {
1284
                deferred.reject(e);
1285
            }
1286
        )
1287
        ;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1288
    });
1289
1290
    return deferred.promise;
1291
};
1292
1293 View Code Duplication
APIClient.prototype._createNewWalletV2 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1294
    var self = this;
1295
1296
    var deferred = q.defer();
1297
1298
    // avoid modifying passed options
1299
    options = _.merge({}, options);
1300
1301
    determineDataStorageV2_3(options)
1302
        .then(function(options) {
1303
            options.passphrase = options.passphrase || options.password;
1304
            delete options.password;
1305
1306
            // avoid deprecated options
1307
            if (options.primaryPrivateKey) {
1308
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1309
            }
1310
1311
            // seed should be provided or generated
1312
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1313
1314
            return options;
1315
        })
1316
        .then(function(options) {
1317
            return produceEncryptedDataV2(options, deferred.notify.bind(deferred));
1318
        })
1319
        .then(function(options) {
1320
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1321
        })
1322
        .then(function(options) {
1323
            // create a checksum of our private key which we'll later use to verify we used the right password
1324
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1325
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1326
            var keyIndex = options.keyIndex;
1327
1328
            // send the public keys and encrypted data to server
1329
            return self.storeNewWalletV2(
1330
                options.identifier,
1331
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1332
                [options.backupPublicKey.toBase58(), "M"],
1333
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1334
                options.storeDataOnServer ? options.encryptedSecret : false,
1335
                options.storeDataOnServer ? options.recoverySecret : false,
1336
                checksum,
1337
                keyIndex,
1338
                options.support_secret || null,
1339
                options.segwit || null
1340
            )
1341
                .then(
1342
                function(result) {
1343
                    deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1344
1345
                    var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1346
                        return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1347
                    });
1348
1349
                    var wallet = new Wallet(
1350
                        self,
1351
                        options.identifier,
1352
                        Wallet.WALLET_VERSION_V2,
1353
                        null,
1354
                        options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1355
                        options.storeDataOnServer ? options.encryptedSecret : null,
1356
                        {keyIndex: options.primaryPublicKey},
1357
                        options.backupPublicKey,
1358
                        blocktrailPublicKeys,
1359
                        keyIndex,
1360
                        result.segwit || 0,
1361
                        self.testnet,
1362
                        self.regtest,
1363
                        checksum,
1364
                        result.upgrade_key_index,
1365
                        options.useCashAddress,
1366
                        options.bypassNewAddressCheck
1367
                    );
1368
1369
                    // pass along decrypted data to avoid extra work
1370
                    return wallet.unlock({
1371
                        walletVersion: Wallet.WALLET_VERSION_V2,
1372
                        passphrase: options.passphrase,
1373
                        primarySeed: options.primarySeed,
1374
                        secret: options.secret
1375
                    }).then(function() {
1376
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1377
                        return [
1378
                            wallet,
1379
                            {
1380
                                walletVersion: wallet.walletVersion,
1381
                                encryptedPrimarySeed: options.encryptedPrimarySeed ?
1382
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedPrimarySeed, 'base64', 'hex')) :
1383
                                    null,
1384
                                backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed.toString('hex')) : null,
1385
                                recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1386
                                    bip39.entropyToMnemonic(blocktrail.convert(options.recoveryEncryptedSecret, 'base64', 'hex')) :
1387
                                    null,
1388
                                encryptedSecret: options.encryptedSecret ?
1389
                                    bip39.entropyToMnemonic(blocktrail.convert(options.encryptedSecret, 'base64', 'hex')) :
1390
                                    null,
1391
                                blocktrailPublicKeys: blocktrailPublicKeys
1392
                            }
1393
                        ];
1394
                    });
1395
                }
1396
            );
1397
        })
1398
       .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1399
1400
    return deferred.promise;
1401
};
1402
1403 View Code Duplication
APIClient.prototype._createNewWalletV3 = function(options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1404
    var self = this;
1405
1406
    var deferred = q.defer();
1407
1408
    // avoid modifying passed options
1409
    options = _.merge({}, options);
1410
1411
    determineDataStorageV2_3(options)
1412
        .then(function(options) {
1413
            options.passphrase = options.passphrase || options.password;
1414
            delete options.password;
1415
1416
            // avoid deprecated options
1417
            if (options.primaryPrivateKey) {
1418
                throw new blocktrail.WalletInitError("Can't specify; Primary PrivateKey");
1419
            }
1420
1421
            // seed should be provided or generated
1422
            options.primarySeed = options.primarySeed || randomBytes(Wallet.WALLET_ENTROPY_BITS / 8);
1423
1424
            return options;
1425
        })
1426
        .then(function(options) {
1427
            return self.produceEncryptedDataV3(options, deferred.notify.bind(deferred));
1428
        })
1429
        .then(function(options) {
1430
            return doRemainingWalletDataV2_3(options, self.network, deferred.notify.bind(deferred));
1431
        })
1432
        .then(function(options) {
1433
            // create a checksum of our private key which we'll later use to verify we used the right password
1434
            var pubKeyHash = bitcoin.crypto.hash160(options.primaryPrivateKey.getPublicKeyBuffer());
1435
            var checksum = bitcoin.address.toBase58Check(pubKeyHash, self.network.pubKeyHash);
1436
            var keyIndex = options.keyIndex;
1437
1438
            // send the public keys and encrypted data to server
1439
            return self.storeNewWalletV3(
1440
                options.identifier,
1441
                [options.primaryPublicKey.toBase58(), "M/" + keyIndex + "'"],
1442
                [options.backupPublicKey.toBase58(), "M"],
1443
                options.storeDataOnServer ? options.encryptedPrimarySeed : false,
1444
                options.storeDataOnServer ? options.encryptedSecret : false,
1445
                options.storeDataOnServer ? options.recoverySecret : false,
1446
                checksum,
1447
                keyIndex,
1448
                options.support_secret || null,
1449
                options.segwit || null
1450
            )
1451
                .then(
1452
                    // result, deferred, self(apiclient)
1453
                    function(result) {
1454
                        deferred.notify(APIClient.CREATE_WALLET_PROGRESS_INIT);
1455
1456
                        var blocktrailPublicKeys = _.mapValues(result.blocktrail_public_keys, function(blocktrailPublicKey) {
1457
                            return bitcoin.HDNode.fromBase58(blocktrailPublicKey[0], self.network);
1458
                        });
1459
1460
                        var wallet = new Wallet(
1461
                            self,
1462
                            options.identifier,
1463
                            Wallet.WALLET_VERSION_V3,
1464
                            null,
1465
                            options.storeDataOnServer ? options.encryptedPrimarySeed : null,
1466
                            options.storeDataOnServer ? options.encryptedSecret : null,
1467
                            {keyIndex: options.primaryPublicKey},
1468
                            options.backupPublicKey,
1469
                            blocktrailPublicKeys,
1470
                            keyIndex,
1471
                            result.segwit || 0,
1472
                            self.testnet,
1473
                            self.regtest,
1474
                            checksum,
1475
                            result.upgrade_key_index,
1476
                            options.useCashAddress,
1477
                            options.bypassNewAddressCheck
1478
                        );
1479
1480
                        // pass along decrypted data to avoid extra work
1481
                        return wallet.unlock({
1482
                            walletVersion: Wallet.WALLET_VERSION_V3,
1483
                            passphrase: options.passphrase,
1484
                            primarySeed: options.primarySeed,
1485
                            secret: options.secret
1486
                        }).then(function() {
1487
                            deferred.notify(APIClient.CREATE_WALLET_PROGRESS_DONE);
1488
                            return [
1489
                                wallet,
1490
                                {
1491
                                    walletVersion: wallet.walletVersion,
1492
                                    encryptedPrimarySeed: options.encryptedPrimarySeed ? EncryptionMnemonic.encode(options.encryptedPrimarySeed) : null,
1493
                                    backupSeed: options.backupSeed ? bip39.entropyToMnemonic(options.backupSeed) : null,
1494
                                    recoveryEncryptedSecret: options.recoveryEncryptedSecret ?
1495
                                        EncryptionMnemonic.encode(options.recoveryEncryptedSecret) : null,
1496
                                    encryptedSecret: options.encryptedSecret ? EncryptionMnemonic.encode(options.encryptedSecret) : null,
1497
                                    blocktrailPublicKeys: blocktrailPublicKeys
1498
                                }
1499
                            ];
1500
                        });
1501
                    }
1502
                );
1503
        })
1504
        .then(function(r) { deferred.resolve(r); }, function(e) { deferred.reject(e); });
1505
1506
    return deferred.promise;
1507
};
1508
1509
function verifyPublicBip32Key(bip32Key, network) {
1510
    var hk = bitcoin.HDNode.fromBase58(bip32Key[0], network);
1511
    if (typeof hk.keyPair.d !== "undefined") {
1512
        throw new Error('BIP32Key contained private key material - abort');
1513
    }
1514
1515
    if (bip32Key[1].slice(0, 1) !== "M") {
1516
        throw new Error("BIP32Key contained non-public path - abort");
1517
    }
1518
}
1519
1520
function verifyPublicOnly(walletData, network) {
1521
    verifyPublicBip32Key(walletData.primary_public_key, network);
1522
    verifyPublicBip32Key(walletData.backup_public_key, network);
1523
}
1524
1525
/**
1526
 * create wallet using the API
1527
 *
1528
 * @param identifier            string      the wallet identifier to create
1529
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1530
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1531
 * @param primaryMnemonic       string      mnemonic to store
1532
 * @param checksum              string      checksum to store
1533
 * @param keyIndex              int         keyIndex that was used to create wallet
1534
 * @param segwit                bool
1535
 * @returns {q.Promise}
1536
 */
1537
APIClient.prototype.storeNewWalletV1 = function(identifier, primaryPublicKey, backupPublicKey, primaryMnemonic,
1538
                                                checksum, keyIndex, segwit) {
1539
    var self = this;
1540
1541
    var postData = {
1542
        identifier: identifier,
1543
        wallet_version: Wallet.WALLET_VERSION_V1,
1544
        primary_public_key: primaryPublicKey,
1545
        backup_public_key: backupPublicKey,
1546
        primary_mnemonic: primaryMnemonic,
1547
        checksum: checksum,
1548
        key_index: keyIndex,
1549
        segwit: segwit
1550
    };
1551
1552
    verifyPublicOnly(postData, self.network);
1553
1554
    return self.client.post("/wallet", null, postData);
1555
};
1556
1557
/**
1558
 * create wallet using the API
1559
 *
1560
 * @param identifier            string      the wallet identifier to create
1561
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1562
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1563
 * @param encryptedPrimarySeed  string      openssl format
1564
 * @param encryptedSecret       string      openssl format
1565
 * @param recoverySecret        string      openssl format
1566
 * @param checksum              string      checksum to store
1567
 * @param keyIndex              int         keyIndex that was used to create wallet
1568
 * @param supportSecret         string
1569
 * @param segwit                bool
1570
 * @returns {q.Promise}
1571
 */
1572
APIClient.prototype.storeNewWalletV2 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1573
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1574
    var self = this;
1575
1576
    var postData = {
1577
        identifier: identifier,
1578
        wallet_version: Wallet.WALLET_VERSION_V2,
1579
        primary_public_key: primaryPublicKey,
1580
        backup_public_key: backupPublicKey,
1581
        encrypted_primary_seed: encryptedPrimarySeed,
1582
        encrypted_secret: encryptedSecret,
1583
        recovery_secret: recoverySecret,
1584
        checksum: checksum,
1585
        key_index: keyIndex,
1586
        support_secret: supportSecret || null,
1587
        segwit: segwit
1588
    };
1589
1590
    verifyPublicOnly(postData, self.network);
1591
1592
    return self.client.post("/wallet", null, postData);
1593
};
1594
1595
/**
1596
 * create wallet using the API
1597
 *
1598
 * @param identifier            string      the wallet identifier to create
1599
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1600
 * @param backupPublicKey       array       the backup public key - [key, path] should be M/<keyIndex>'
1601
 * @param encryptedPrimarySeed  Buffer      buffer of ciphertext
1602
 * @param encryptedSecret       Buffer      buffer of ciphertext
1603
 * @param recoverySecret        Buffer      buffer of recovery secret
1604
 * @param checksum              string      checksum to store
1605
 * @param keyIndex              int         keyIndex that was used to create wallet
1606
 * @param supportSecret         string
1607
 * @param segwit                bool
1608
 * @returns {q.Promise}
1609
 */
1610
APIClient.prototype.storeNewWalletV3 = function(identifier, primaryPublicKey, backupPublicKey, encryptedPrimarySeed, encryptedSecret,
1611
                                                recoverySecret, checksum, keyIndex, supportSecret, segwit) {
1612
    var self = this;
1613
1614
    var postData = {
1615
        identifier: identifier,
1616
        wallet_version: Wallet.WALLET_VERSION_V3,
1617
        primary_public_key: primaryPublicKey,
1618
        backup_public_key: backupPublicKey,
1619
        encrypted_primary_seed: encryptedPrimarySeed.toString('base64'),
1620
        encrypted_secret: encryptedSecret.toString('base64'),
1621
        recovery_secret: recoverySecret.toString('hex'),
1622
        checksum: checksum,
1623
        key_index: keyIndex,
1624
        support_secret: supportSecret || null,
1625
        segwit: segwit
1626
    };
1627
1628
    verifyPublicOnly(postData, self.network);
1629
1630
    return self.client.post("/wallet", null, postData);
1631
};
1632
1633
/**
1634
 * create wallet using the API
1635
 *
1636
 * @param identifier            string      the wallet identifier to create
1637
 * @param postData              object
1638
 * @param [cb]                  function    callback(err, result)
1639
 * @returns {q.Promise}
1640
 */
1641
APIClient.prototype.updateWallet = function(identifier, postData, cb) {
1642
    var self = this;
1643
1644
    return self.client.post("/wallet/" + identifier, null, postData, cb);
1645
};
1646
1647
/**
1648
 * upgrade wallet to use a new account number
1649
 *  the account number specifies which blocktrail cosigning key is used
1650
 *
1651
 * @param identifier            string      the wallet identifier
1652
 * @param primaryPublicKey      array       the primary public key - [key, path] should be M/<keyIndex>'
1653
 * @param keyIndex              int         keyIndex that was used to create wallet
1654
 * @param [cb]                  function    callback(err, result)
1655
 * @returns {q.Promise}
1656
 */
1657
APIClient.prototype.upgradeKeyIndex = function(identifier, keyIndex, primaryPublicKey, cb) {
1658
    var self = this;
1659
1660
    return self.client.post("/wallet/" + identifier + "/upgrade", null, {
1661
        key_index: keyIndex,
1662
        primary_public_key: primaryPublicKey
1663
    }, cb);
1664
};
1665
1666
/**
1667
 * get the balance for the wallet
1668
 *
1669
 * @param identifier            string      the wallet identifier
1670
 * @param [cb]                  function    callback(err, result)
1671
 * @returns {q.Promise}
1672
 */
1673
APIClient.prototype.getWalletBalance = function(identifier, cb) {
1674
    var self = this;
1675
1676
    return self.client.get("/wallet/" + identifier + "/balance", null, true, cb);
1677
};
1678
1679
/**
1680
 * get a new derivation number for specified parent path
1681
 *  eg; m/44'/1'/0/0 results in m/44'/1'/0/0/0 and next time in m/44'/1'/0/0/1 and next time in m/44'/1'/0/0/2
1682
 *
1683
 * @param identifier            string      the wallet identifier
1684
 * @param path                  string      the parent path for which to get a new derivation,
1685
 *                                           can be suffixed with /* to make it clear on which level the derivations hould be
1686
 * @param [cb]                  function    callback(err, result)
1687
 * @returns {q.Promise}
1688
 */
1689
APIClient.prototype.getNewDerivation = function(identifier, path, cb) {
1690
    var self = this;
1691
1692
    return self.client.post("/wallet/" + identifier + "/path", null, {path: path}, cb);
1693
};
1694
1695
1696
/**
1697
 * delete the wallet
1698
 *  the checksum address and a signature to verify you ownership of the key of that checksum address
1699
 *  is required to be able to delete a wallet
1700
 *
1701
 * @param identifier            string      the wallet identifier
1702
 * @param checksumAddress       string      the address for your master private key (and the checksum used when creating the wallet)
1703
 * @param checksumSignature     string      a signature of the checksum address as message signed by the private key matching that address
1704
 * @param [force]               bool        ignore warnings (such as a non-zero balance)
1705
 * @param [cb]                  function    callback(err, result)
1706
 * @returns {q.Promise}
1707
 */
1708
APIClient.prototype.deleteWallet = function(identifier, checksumAddress, checksumSignature, force, cb) {
1709
    var self = this;
1710
1711
    if (typeof force === "function") {
1712
        cb = force;
1713
        force = false;
1714
    }
1715
1716
    return self.client.delete("/wallet/" + identifier, {force: force}, {
1717
        checksum: checksumAddress,
1718
        signature: checksumSignature
1719
    }, cb);
1720
};
1721
1722
/**
1723
 * use the API to get the best inputs to use based on the outputs
1724
 *
1725
 * the return array has the following format:
1726
 * [
1727
 *  "utxos" => [
1728
 *      [
1729
 *          "hash" => "<txHash>",
1730
 *          "idx" => "<index of the output of that <txHash>",
1731
 *          "scriptpubkey_hex" => "<scriptPubKey-hex>",
1732
 *          "value" => 32746327,
1733
 *          "address" => "1address",
1734
 *          "path" => "m/44'/1'/0'/0/13",
1735
 *          "redeem_script" => "<redeemScript-hex>",
1736
 *      ],
1737
 *  ],
1738
 *  "fee"   => 10000,
1739
 *  "change"=> 1010109201,
1740
 * ]
1741
 *
1742
 * @param identifier        string      the wallet identifier
1743
 * @param pay               array       {'address': (int)value}     coins to send
1744
 * @param lockUTXO          bool        lock UTXOs for a few seconds to allow for transaction to be created
1745
 * @param allowZeroConf     bool        allow zero confirmation unspent outputs to be used in coin selection
1746
 * @param feeStrategy       string      defaults to
1747
 * @param options
1748
 * @param [cb]              function    callback(err, utxos, fee, change)
1749
 * @returns {q.Promise}
1750
 */
1751
APIClient.prototype.coinSelection = function(identifier, pay, lockUTXO, allowZeroConf, feeStrategy, options, cb) {
1752
    var self = this;
1753
1754
    if (typeof feeStrategy === "function") {
1755
        cb = feeStrategy;
1756
        feeStrategy = null;
1757
        options = {};
1758
    } else if (typeof options === "function") {
1759
        cb = options;
1760
        options = {};
1761
    }
1762
1763
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1764
    options = options || {};
1765
1766
    var deferred = q.defer();
1767
    deferred.promise.spreadNodeify(cb);
1768
1769
    var params = {
1770
        lock: lockUTXO,
1771
        zeroconf: allowZeroConf ? 1 : 0,
1772
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1773
        fee_strategy: feeStrategy
1774
    };
1775
1776
    if (options.forcefee) {
1777
        params['forcefee'] = options.forcefee;
1778
    }
1779
1780
    deferred.resolve(
1781
        self.client.post("/wallet/" + identifier + "/coin-selection", params, pay).then(
1782
            function(result) {
1783
                return [result.utxos, result.fee, result.change, result];
1784
            },
1785
            function(err) {
1786
                if (err.message.match(/too low to pay the fee/)) {
1787
                    throw blocktrail.WalletFeeError(err);
1788
                }
1789
1790
                throw err;
1791
            }
1792
        )
1793
    );
1794
1795
    return deferred.promise;
1796
};
1797
1798
/**
1799
 * @param [cb]              function    callback(err, utxos, fee, change)
1800
 * @returns {q.Promise}
1801
 */
1802
APIClient.prototype.feePerKB = function(cb) {
1803
    var self = this;
1804
1805
    var deferred = q.defer();
1806
    deferred.promise.spreadNodeify(cb);
1807
1808
    deferred.resolve(self.client.get("/fee-per-kb"));
1809
1810
    return deferred.promise;
1811
};
1812
1813
/**
1814
 * send the transaction using the API
1815
 *
1816
 * @param identifier        string      the wallet identifier
1817
 * @param txHex             string      partially signed transaction as hex string
1818
 * @param paths             array       list of paths used in inputs which should be cosigned by the API
1819
 * @param checkFee          bool        when TRUE the API will verify if the fee is 100% correct and otherwise throw an exception
1820
 * @param [twoFactorToken]  string      2FA token
1821
 * @param [prioboost]       bool
1822
 * @param [cb]              function    callback(err, txHash)
1823
 * @returns {q.Promise}
1824
 */
1825
APIClient.prototype.sendTransaction = function(identifier, txHex, paths, checkFee, twoFactorToken, prioboost, cb) {
1826
    var self = this;
1827
1828
    if (typeof twoFactorToken === "function") {
1829
        cb = twoFactorToken;
1830
        twoFactorToken = null;
1831
        prioboost = false;
1832
    } else if (typeof prioboost === "function") {
1833
        cb = prioboost;
1834
        prioboost = false;
1835
    }
1836
1837
    var data = {
1838
        paths: paths,
1839
        two_factor_token: twoFactorToken
1840
    };
1841
    if (typeof txHex === "string") {
1842
        data.raw_transaction = txHex;
1843
    } else if (typeof txHex === "object") {
1844
        Object.keys(txHex).map(function(key) {
1845
            data[key] = txHex[key];
1846
        });
1847
    }
1848
1849
    return self.client.post(
1850
        "/wallet/" + identifier + "/send",
1851
        {
1852
            check_fee: checkFee ? 1 : 0,
1853
            prioboost: prioboost ? 1 : 0
1854
        },
1855
        data,
1856
        cb
1857
    );
1858
};
1859
1860
/**
1861
 * setup a webhook for this wallet
1862
 *
1863
 * @param identifier        string      the wallet identifier
1864
 * @param webhookIdentifier string      identifier for the webhook
1865
 * @param url               string      URL to receive webhook events
1866
 * @param [cb]              function    callback(err, webhook)
1867
 * @returns {q.Promise}
1868
 */
1869
APIClient.prototype.setupWalletWebhook = function(identifier, webhookIdentifier, url, cb) {
1870
    var self = this;
1871
1872
    return self.client.post("/wallet/" + identifier + "/webhook", null, {url: url, identifier: webhookIdentifier}, cb);
1873
};
1874
1875
/**
1876
 * delete a webhook that was created for this wallet
1877
 *
1878
 * @param identifier        string      the wallet identifier
1879
 * @param webhookIdentifier string      identifier for the webhook
1880
 * @param [cb]              function    callback(err, success)
1881
 * @returns {q.Promise}
1882
 */
1883
APIClient.prototype.deleteWalletWebhook = function(identifier, webhookIdentifier, cb) {
1884
    var self = this;
1885
1886
    return self.client.delete("/wallet/" + identifier + "/webhook/" + webhookIdentifier, null, null, cb);
1887
};
1888
1889
/**
1890
 * get all transactions for an wallet (paginated)
1891
 *
1892
 * @param identifier    string      wallet identifier
1893
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1894
 * @param [cb]          function    callback function to call when request is complete
1895
 * @return q.Promise
1896
 */
1897
APIClient.prototype.walletTransactions = function(identifier, params, cb) {
1898
    var self = this;
1899
1900
    if (typeof params === "function") {
1901
        cb = params;
1902
        params = null;
1903
    }
1904
1905
    return self.client.get("/wallet/" + identifier + "/transactions", params, true, cb);
1906
};
1907
1908
/**
1909
 * get all addresses for an wallet (paginated)
1910
 *
1911
 * @param identifier    string      wallet identifier
1912
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1913
 * @param [cb]          function    callback function to call when request is complete
1914
 * @return q.Promise
1915
 */
1916
APIClient.prototype.walletAddresses = function(identifier, params, cb) {
1917
    var self = this;
1918
1919
    if (typeof params === "function") {
1920
        cb = params;
1921
        params = null;
1922
    }
1923
1924
    return self.client.get("/wallet/" + identifier + "/addresses", params, true, cb);
1925
};
1926
1927
/**
1928
 * @param identifier    string      wallet identifier
1929
 * @param address       string      the address to label
1930
 * @param label         string      the label
1931
 * @param [cb]          function    callback(err, res)
1932
 * @return q.Promise
1933
 */
1934
APIClient.prototype.labelWalletAddress = function(identifier, address, label, cb) {
1935
    var self = this;
1936
1937
    return self.client.post("/wallet/" + identifier + "/address/" + address + "/label", null, {label: label}, cb);
1938
};
1939
1940
APIClient.prototype.walletMaxSpendable = function(identifier, allowZeroConf, feeStrategy, options, cb) {
1941
    var self = this;
1942
1943
    if (typeof feeStrategy === "function") {
1944
        cb = feeStrategy;
1945
        feeStrategy = null;
1946
    } else if (typeof options === "function") {
1947
        cb = options;
1948
        options = {};
1949
    }
1950
1951
    feeStrategy = feeStrategy || Wallet.FEE_STRATEGY_OPTIMAL;
1952
    options = options || {};
1953
1954
    var params = {
1955
        outputs: options.outputs ? options.outputs : 1,
1956
        zeroconf: allowZeroConf ? 1 : 0,
1957
        zeroconfself: (typeof options.allowZeroConfSelf !== "undefined" ? options.allowZeroConfSelf : true) ? 1 : 0,
1958
        fee_strategy: feeStrategy
1959
    };
1960
1961
    if (options.forcefee) {
1962
        params['forcefee'] = options.forcefee;
1963
    }
1964
1965
    return self.client.get("/wallet/" + identifier + "/max-spendable", params, true, cb);
1966
};
1967
1968
/**
1969
 * get all UTXOs for an wallet (paginated)
1970
 *
1971
 * @param identifier    string      wallet identifier
1972
 * @param [params]      object      pagination: {page: 1, limit: 20, sort_dir: 'asc'}
1973
 * @param [cb]          function    callback function to call when request is complete
1974
 * @return q.Promise
1975
 */
1976
APIClient.prototype.walletUTXOs = function(identifier, params, cb) {
1977
    var self = this;
1978
1979
    if (typeof params === "function") {
1980
        cb = params;
1981
        params = null;
1982
    }
1983
1984
    return self.client.get("/wallet/" + identifier + "/utxos", params, true, cb);
1985
};
1986
1987
/**
1988
 * get a paginated list of all wallets associated with the api user
1989
 *
1990
 * @param [params]      object      pagination: {page: 1, limit: 20}
1991
 * @param [cb]          function    callback function to call when request is complete
1992
 * @return q.Promise
1993
 */
1994
APIClient.prototype.allWallets = function(params, cb) {
1995
    var self = this;
1996
1997
    if (typeof params === "function") {
1998
        cb = params;
1999
        params = null;
2000
    }
2001
2002
    return self.client.get("/wallets", params, true, cb);
2003
};
2004
2005
/**
2006
 * verify a message signed bitcoin-core style
2007
 *
2008
 * @param message        string
2009
 * @param address        string
2010
 * @param signature      string
2011
 * @param [cb]          function    callback function to call when request is complete
2012
 * @return q.Promise
2013
 */
2014
APIClient.prototype.verifyMessage = function(message, address, signature, cb) {
2015
    var self = this;
2016
2017
    // we could also use the API instead of the using bitcoinjs-lib to verify
2018
    // return self.client.post("/verify_message", null, {message: message, address: address, signature: signature}, cb);
2019
2020
    var deferred = q.defer();
2021
    deferred.promise.nodeify(cb);
2022
    try {
2023
        var result = bitcoinMessage.verify(address, self.network.messagePrefix, message, new Buffer(signature, 'base64'));
0 ignored issues
show
Bug introduced by
The variable Buffer seems to be never declared. If this is a global, consider adding a /** global: Buffer */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
2024
        deferred.resolve(result);
2025
    } catch (e) {
2026
        deferred.reject(e);
2027
    }
2028
2029
    return deferred.promise;
2030
};
2031
2032
/**
2033
 * max is 0.001
2034
 * testnet only
2035
 *
2036
 * @param address
2037
 * @param amount
2038
 * @param cb
2039
 */
2040
APIClient.prototype.faucetWithdrawl = function(address, amount, cb) {
2041
    var self = this;
2042
2043
    return self.client.post("/faucet/withdrawl", null, {address: address, amount: amount}, cb);
2044
};
2045
2046
/**
2047
 * send a raw transaction
2048
 *
2049
 * @param rawTransaction    string      raw transaction as HEX
2050
 * @param [cb]              function    callback function to call when request is complete
2051
 * @return q.Promise
2052
 */
2053
APIClient.prototype.sendRawTransaction = function(rawTransaction, cb) {
2054
    var self = this;
2055
2056
    return self.client.post("/send-raw-tx", null, rawTransaction, cb);
2057
};
2058
2059
/**
2060
 * get the current price index
2061
 *
2062
 * @param [cb]          function    callback({'USD': 287.30})
2063
 * @return q.Promise
2064
 */
2065
APIClient.prototype.price = function(cb) {
2066
    var self = this;
2067
2068
    return self.client.get("/price", null, false, cb);
2069
};
2070
2071
module.exports = APIClient;
2072